package com.monochromebytes.mononet.packet;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;

/**
 * Sends packages fetched from an Outbox with a DatagramChannel.
 * 
 * In its run() method it sends packets in a while loop. It stops when the
 * Thread has been interrupted.
 * 
 * An occouring IOException closes the whole UDPSocket.
 */
public class UDPSocketSender implements Runnable, OutboxListener {

    /**
     * The socket that has started this worker and handles the channel.
     */
    private final UDPSocket socket;

    /**
     * The outbox outgoing packets will be fetched from.
     */
    private final Outbox    outbox;

    /**
     * Creates a new UDPSocketSender that uses the given Outbox for the transfer
     * of packets.
     * 
     * The UDPSocketSender is not bound to an UDP port and IP address.
     * 
     * @param socket
     *            The socket received Packets will be put in.
     * @param outbox
     *            The Outbox the UDPSocketWorker will fetch the packets from to
     *            send them.
     */
    public UDPSocketSender(UDPSocket socket, Outbox outbox) {
        this.socket = socket;
        this.outbox = outbox;
        this.outbox.addOutboxListener(this);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Runnable#run()
     */
    @Override
    public void run() {
        while (!Thread.interrupted() && this.socket.isOpen()) {
            try {
                DatagramChannel channel = this.socket.getChannel();

                // Fetch the next packet.
                OutgoingPacket packet = this.outbox.getNextPacket();

                // Check if there is a packet to send.
                if (packet != null) {
                    ByteBuffer buffer = packet.getData();
                    InetSocketAddress recipient = packet.getRecipient();

                    // Send the packet out.
                    int sent = 0;
                    while (sent == 0) {
                        sent = channel.send(buffer, recipient);
                    }

                    // Mark packet as sent if it has left.
                    packet.setSentNow();
                }
                else {
                    // Wait for a certain time.
                    // Thread.sleep(0, 1);
                    synchronized (this) {
                        this.wait();
                    }
                }
            } catch (IOException e) {
                try {
                    // Close the socket if any I/O Exception has been thrown.
                    this.socket.close(e);
                } catch (IOException eClose) {
                    eClose.printStackTrace();
                }
            } catch (InterruptedException e) {
                // Nothing to do.
            }
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * com.monochromebytes.mononet.packet.OutboxListener#outgoingPackedAdded()
     */
    @Override
    public void outgoingPackedAdded() {
        synchronized (this) {
            this.notifyAll();
        }
    }
}